#include "GPGPU_FFT.h"
#include "OPT_FFT_kernel.h"
#include "timer.h"

#define GPGPU_GLOBAL_BUFFER_PROJECTION_NUM (32)
#define GPGPU_DATA_LINE_SHIFT 2
#define GPGPU_DATA_LINE_WIDTH 8192

ProgramInfo fftProgram = ProgramInfo("FFT", OPT_FFT_KERNEL).constants(1).hasGlobal();
ProgramInfo inAsmTblProgram = ProgramInfo("Input Assambly Table", _INPUT_ASM_TBL_GEN_KERNEL_).outputs(1).constants(1);

static void dump_real_dataset(float *data, DataDesc *dd) {
	for(int i=0;i<dd->projection;i++) {
		for(int j=0;j<dd->height;j++) {
			dump_real(data, i, dd->height, j, dd->width, 12 );
		}
	}
}

static void dump_cplx_dataset(float *data, DataDesc *dd) {
	for(int i=0;i<dd->projection;i++) {
		for(int j=0;j<dd->height/2;j++) {
			dump_cplx(data, i, dd->height/2, j, dd->get_fft_row()*2, 16 );
		}
	}
}

static void dump_cplx_dataset_int(float *data, DataDesc *dd) {
	for(int i=0;i<dd->projection;i++) {
		for(int j=0;j<dd->height/2;j++) {
			dump_cplx_int(data, i, dd->height/2, j, dd->get_fft_row()*2, 66 );
		}
	}
}

void GPGPU_FFT_Manager::generate_input_asm_table(int size)
{
    Device* device = this->devMgr->getDefaultDevice();
    assert(device);

    Program *prog = this->progMgr->loadProgram(inAsmTblProgram);
    assert(prog);

    this->input_asm_Buf = bufMgr->createLocalBuffer(device, CAL_FORMAT_INT_1, size);
    assert(this->input_asm_Buf);
    ConstBuffer *cb = bufMgr->getConstBuffer(1);
    assert(cb);

    int bit;
    for (bit = 0; size >> bit; bit++){;}
    bit--;

    cb->setConstant<0>(&bit);

    prog->bindOutput(input_asm_Buf, 0);
    prog->bindConstant(cb, 0);

    prog->setExeInfo(ProgExeInfo(uint4(0, 0, size, 1)));
    Event *e = prog->run();
    assert(e);

    //vector<int> data(size);
    //input_asm_Buf->writeData(&data[0], size);

    //for (int i = 0; i < size; i++)
    //{
    //    char buf[16];
    //    itoa(i, buf, 2);
    //    printf("%s: ", buf);
    //    itoa(data[i], buf, 2);
    //    printf("%s\n", buf);
    //}

    this->progMgr->unloadProgram(prog);
    bufMgr->releaseConstBuffer(cb);

    unsigned int buffer_size =
        GPGPU_GLOBAL_BUFFER_PROJECTION_NUM * data_desc->height * data_desc->width;
    in_Buf = bufMgr->createRemoteBuffer(CAL_FORMAT_FLOAT_2, data_desc->width, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM * data_desc->height);
    assert(in_Buf);
}

bool GPGPU_FFT_Manager::gpgpu_init() {

		/* init CAL runtime*/
	    Runtime *runtime = Runtime::getInstance();
		this->devMgr  = runtime->getDeviceManager();
		if (!this->devMgr) {
			return false;
		}
		this->bufMgr = runtime->getBufferManager();
		if (!this->bufMgr) {
			return false;
		}
		this->progMgr = runtime->getProgramManager();
   		if (!this->progMgr) {
			return false;
		}
		this->devMgr->assignDevice(0);
		Device* device = this->devMgr->getDefaultDevice();

		CALdevicestatus dstatus = device->getStatus();
		cout<<"uncached remote RAM "<<dstatus.availUncachedRemoteRAM<<"MB, cached remote RAM "<<dstatus.availCachedRemoteRAM<<"MB"<<endl;

		CALdeviceattribs dattr = device->getAttribs();
		cout<<"cached remote RAM by attribute " << dattr.cachedRemoteRAM << "MB"<<endl;

        //generate the input assembly table for bit reverse.
        generate_input_asm_table(data_desc->width);

		this->prog = this->progMgr->loadProgram(fftProgram);
		unsigned int buffer_size = (GPGPU_GLOBAL_BUFFER_PROJECTION_NUM*data_desc->height*data_desc->get_fft_row())>>GPGPU_DATA_LINE_SHIFT;
		if(buffer_size%GPGPU_DATA_LINE_WIDTH) {
			//buffer_size = (buffer_size/GPGPU_DATA_LINE_WIDTH+1)*GPGPU_DATA_LINE_WIDTH;
			cout<<"Error buffer size is not a multiple of 8192 : "<< buffer_size <<endl;
			return false;
		}
		this->global_buffer_size = buffer_size;
		this->reading_Buf = 
            bufMgr->createLocalBuffer(device, CAL_FORMAT_FLOAT_4, GPGPU_DATA_LINE_WIDTH, buffer_size/GPGPU_DATA_LINE_WIDTH, CAL_RESALLOC_GLOBAL_BUFFER);
		this->computing_Buf = 
            bufMgr->createLocalBuffer(device, CAL_FORMAT_FLOAT_4, GPGPU_DATA_LINE_WIDTH, buffer_size/GPGPU_DATA_LINE_WIDTH, CAL_RESALLOC_GLOBAL_BUFFER);
		return true;
}

void GPGPU_FFT_Manager::gpgpu_init_const() {
    //bind const
	this->const_Buf = bufMgr->getConstBuffer(CONST_SIZE);
	unsigned int filter_counter = 0;

    for(unsigned int ci = 0;ci<CONST_SIZE; ci++)
	{
        amdspl::int4 constant;
		if(ci<SWITCH_SHEET_SIZE ) {
			constant.x = switch_sheet[ci];
		}
        if(ci<SIN_SHEET_SIZE) {
            constant.y = sin_sheet[ci];
        }
		if(ci<FILTER_SHEET_SIZE) {
			constant.z = filter_sheet[ci*2];
			constant.w = filter_sheet[ci*2+1];
		}
        this->const_Buf->setConstant(ci, &constant); // how to set.....
	}
	this->prog->bindConstant(const_Buf, 0);
}

/*
void GPGPU_FFT_Manager::gpgpu_prepare_data(float *input_data) {
	
	dump_real_dataset(input_data, data_desc);
	gpgpu_opt_format_data(input_data);
	dump_cplx_dataset(formated_data, data_desc);
}
*/

void GPGPU_FFT_Manager::gpgpu_input_assambly(float *input_data)
{
    unsigned int buffer_size =
        GPGPU_GLOBAL_BUFFER_PROJECTION_NUM * data_desc->height * data_desc->width;
    //in_Buf->readData((float2)input_data, buffer_size);

}

void GPGPU_FFT_Manager::gpgpu_synchronized_pinned_run(float *input_data, float *ouput_data) {
	int global_data_size = (data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT;

	if(global_data_size<global_buffer_size) {
		gpgpu_fft_run(input_data, ouput_data);
		return;
	}

	CPerfCounter run_timer;
	CPerfCounter write_back_timer;
	CPerfCounter read_timer;

	Buffer *temp = NULL;
	Event *e = NULL;
	int x_threads = GPGPU_GLOBAL_BUFFER_PROJECTION_NUM;
	int y_threads = data_desc->height/2;

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));

	int read_step = (global_buffer_size<<GPGPU_DATA_LINE_SHIFT)>>DATA_EXTENTION_SHIFT;
	unsigned int buffer_size = (global_buffer_size<<GPGPU_DATA_LINE_SHIFT)*sizeof(float);

	float* data;
	float *target_data;

	#ifdef ATI_OS_VISTA 
	unsigned long inputsize = buffer_size + 4096;
	#else
	unsigned long inputsize = buffer_size+ 256;
	#endif

	data = (float*)malloc(inputsize);

	#ifdef ATI_OS_VISTA 
	target_data = (float*)(((uintptr_t)data + 4095) & (~0xfff));
	#else
	target_data = (float*)(((uintptr_t)data + 255) & (~0xff));
	#endif

	float* write_point = ouput_data;
	float* read_point = input_data;
	float* data_bouder = input_data + this->data_size;

	int counter = 1;
	while((read_point+read_step)<data_bouder) {
		cout << counter << " start reading ..."  << endl;
		read_timer.Reset();
		
		this->gpgpu_format_data(read_point, target_data, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
		//read asynchronizingly data to current global buffer
		//reading_Buf->readData(target_data, global_buffer_size);
		dump_cplx_dataset(target_data, data_desc);

		read_timer.Start();
		((LocalBuffer *)reading_Buf)->readPinnedData(target_data, global_buffer_size);
		read_timer.Stop();
		cout << counter << " reading done, using: " << read_timer.GetElapsedTime() << " s" << endl;

		//bind current global buffer to program
		this->prog->bindGlobal(reading_Buf);

		//run synchonized
		run_timer.Reset();
		run_timer.Start();
		e = this->prog->run();
		e->waitEvent();
		run_timer.Stop();
		cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

		write_back_timer.Reset();
		write_back_timer.Start();
		//write last processed data to main memory
		//this->reading_Buf->writeData(target_data, global_buffer_size);
		((LocalBuffer *)reading_Buf)->writePinnedData(target_data, buffer_size);
		write_back_timer.Stop();

		dump_cplx_dataset(target_data, data_desc);

		gpgpu_deformat_data(target_data, write_point, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
		
		cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;

		write_point += read_step;
		read_point += read_step;
		counter++;

	}

	int remain_data_size = data_bouder - read_point;
	int remain_projection = remain_data_size/(data_desc->height*data_desc->width);
	cout<<"last projections: "<<remain_projection<<endl;
	cout << counter << " start reading ..."  << endl;
	read_timer.Reset();
	this->gpgpu_format_data(read_point, target_data, remain_projection);
	read_timer.Start();
	this->reading_Buf->readData(target_data, remain_data_size>>GPGPU_DATA_LINE_SHIFT);
	read_timer.Stop();
	cout << counter << " reading done, using: " << read_timer.GetElapsedTime() << " s" << endl;
	
	x_threads = remain_projection;

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));
	this->prog->bindGlobal(reading_Buf);

	run_timer.Reset();
	run_timer.Start();
	e = this->prog->run();

	e->waitEvent();
	run_timer.Stop();
	cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

	write_back_timer.Reset();
	write_back_timer.Start();
	//((LocalBuffer *)reading_Buf)->writePinnedData(target_data, (remain_data_size<<DATA_EXTENTION_SHIFT)*sizeof(float));
	((LocalBuffer *)reading_Buf)->writePinnedData(target_data, buffer_size);
	write_back_timer.Stop();

	gpgpu_deformat_data(target_data, write_point, remain_projection);
	cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;

	//free(target_data);
	return;
}

void GPGPU_FFT_Manager::gpgpu_synchronized_run(float *input_data, float *ouput_data) {
	int global_data_size = (data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT;

	if(global_data_size<global_buffer_size) {
		gpgpu_fft_run(input_data, ouput_data);
		return;
	}

	dump_real_dataset(input_data, data_desc);
	CPerfCounter run_timer;
	CPerfCounter write_back_timer;
	CPerfCounter read_timer;

	Buffer *temp = NULL;
	Event *e = NULL;
	int x_threads = GPGPU_GLOBAL_BUFFER_PROJECTION_NUM;
	int y_threads = data_desc->height/2;

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));

	int read_step = (global_buffer_size<<GPGPU_DATA_LINE_SHIFT)>>DATA_EXTENTION_SHIFT;
	float *target_data = (float *)malloc((global_buffer_size<<GPGPU_DATA_LINE_SHIFT)*sizeof(float));

	float* write_point = ouput_data;
	float* read_point = input_data;
	float* data_bouder = input_data + this->data_size;
	int counter = 1;
	while((read_point+read_step)<data_bouder) {
		cout << counter << " start reading ..."  << endl;
		read_timer.Reset();
		read_timer.Start();
		this->gpgpu_format_data(read_point, target_data, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
		//read asynchronizingly data to current global buffer
		this->reading_Buf->readData(target_data, global_buffer_size);
	
		//bind current global buffer to program
		this->prog->bindGlobal(reading_Buf);

		//switch the buffer
		temp = this->computing_Buf;
		this->computing_Buf = this->reading_Buf;
		this->reading_Buf = temp;

		run_timer.Reset();
		//run synchonized
		run_timer.Start();
		e = this->prog->run();

		read_timer.Stop();
		cout << counter << " reading done, using: " << read_timer.GetElapsedTime() << " s" << endl;

		e->waitEvent();
		run_timer.Stop();
		cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

		write_back_timer.Reset();
		write_back_timer.Start();
		//write last processed data to main memory
		this->reading_Buf->writeData(target_data, global_buffer_size);
		gpgpu_deformat_data(target_data, write_point, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
		write_back_timer.Stop();
		cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;
		write_point += read_step;

		read_point += read_step;
		counter++;

	}

	int remain_data_size = data_bouder - read_point;
	int remain_projection = remain_data_size/(data_desc->height*data_desc->width);
	cout<<"last projections: "<<remain_projection<<endl;
	this->gpgpu_format_data(read_point, target_data, remain_projection);
	this->reading_Buf->readData(target_data, (remain_data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT);
	cout << counter << " start reading ..."  << endl;

	x_threads = remain_projection;

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));

	this->prog->bindGlobal(reading_Buf);

	run_timer.Reset();
	run_timer.Start();
	e = this->prog->run();
	e->waitEvent();
	run_timer.Stop();
	cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

	write_back_timer.Reset();
	write_back_timer.Start();
	this->reading_Buf->writeData(target_data, (remain_data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT);
	gpgpu_deformat_data(target_data, write_point, remain_projection);
	write_back_timer.Stop();
	cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;

	free(target_data);
	return;
}

void GPGPU_FFT_Manager::gpgpu_iterative_run(float *input_data, float *ouput_data) {

	int global_data_size = (data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT;

	if(global_data_size<global_buffer_size) {
		gpgpu_fft_run(input_data, ouput_data);
		return;
	}

	dump_real_dataset(input_data, data_desc);
	CPerfCounter run_timer;
	CPerfCounter write_back_timer;
	CPerfCounter test_timer;

	Buffer *temp = NULL;
	Event *e = NULL;
	int x_threads = GPGPU_GLOBAL_BUFFER_PROJECTION_NUM;
	int y_threads = data_desc->height/2;

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));

	int read_step = (global_buffer_size<<GPGPU_DATA_LINE_SHIFT)>>DATA_EXTENTION_SHIFT;
	float *target_data = (float *)malloc((global_buffer_size<<GPGPU_DATA_LINE_SHIFT)*sizeof(float));

	float* write_point = ouput_data;
	float* read_point = input_data;
	float* data_bouder = input_data + this->data_size;
	int counter = 1;
	while((read_point+read_step)<data_bouder) {
		cout << counter << " start reading ..."  << endl;
		this->gpgpu_format_data(read_point, target_data, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
		//read asynchronizingly data to current global buffer
		this->reading_Buf->readData(target_data, global_buffer_size);
		if(e) {
			//wait last computing process
			test_timer.Reset();
			test_timer.Start();
			e->waitEvent();
			test_timer.Stop();
			cout<<counter<< " wait time " << test_timer.GetElapsedTime() << " s" << endl;
			
			counter--;
			run_timer.Stop();
			cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

			write_back_timer.Reset();
			write_back_timer.Start();
			//write last processed data to main memory
			this->reading_Buf->writeData(target_data, global_buffer_size);
			gpgpu_deformat_data(target_data, write_point, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
			write_back_timer.Stop();
			cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;
			write_point += read_step;
			counter++;
		}
		//bind current global buffer to program
		this->prog->bindGlobal(reading_Buf);

		//switch the buffer
		temp = this->computing_Buf;
		this->computing_Buf = this->reading_Buf;
		this->reading_Buf = temp;

		run_timer.Reset();
		//run asynchonizingly
		e = this->prog->run();
		run_timer.Start();
		
		read_point += read_step;
		counter++;
	}

	int remain_data_size = data_bouder - read_point;
	int remain_projection = remain_data_size/(data_desc->height*data_desc->width);
	cout<<"last projections: "<<remain_projection<<endl;
	this->gpgpu_format_data(read_point, target_data, remain_projection);
	this->reading_Buf->readData(target_data, (remain_data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT);
	cout << counter << " start reading ..."  << endl;

	x_threads = remain_projection;

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));

	test_timer.Reset();
	test_timer.Start();
	e->waitEvent();
	test_timer.Stop();
	cout<<counter<< " wait time " << test_timer.GetElapsedTime() << " s" << endl;
	
	counter--;
	run_timer.Stop();
	cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

	write_back_timer.Reset();
	write_back_timer.Start();
	this->computing_Buf->writeData(target_data, global_buffer_size);
	gpgpu_deformat_data(target_data, write_point, GPGPU_GLOBAL_BUFFER_PROJECTION_NUM);
	write_back_timer.Stop();
	cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;
	write_point += read_step;
	counter++;

	this->prog->bindGlobal(reading_Buf);
	run_timer.Reset();
	e = this->prog->run();
	run_timer.Start();
	e->waitEvent();
	run_timer.Stop();
	cout << counter << " computing done, using: " << run_timer.GetElapsedTime() << " s" << endl;

	write_back_timer.Reset();
	write_back_timer.Start();
	this->reading_Buf->writeData(target_data, (remain_data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT);
	gpgpu_deformat_data(target_data, write_point, remain_projection);
	write_back_timer.Stop();
	cout << counter << " writing done, using: " << write_back_timer.GetElapsedTime() << " s" << endl;

	free(target_data);
	return;
}


void GPGPU_FFT_Manager::gpgpu_fft_run(float *input_data, float *output_data) {

	int global_data_size = (data_size<<DATA_EXTENTION_SHIFT)>>GPGPU_DATA_LINE_SHIFT;
	if(global_data_size>global_buffer_size) {
		cout<<"data size="<<global_data_size
			  <<"buffer size="<<global_buffer_size
			  <<", You should use a iterative GPGPU run!" <<endl;
		return;
	}
	dump_real_dataset(input_data, data_desc);

	float *formated_data = (float *)malloc(data_desc->get_fft_size()*sizeof(float));
	memset(formated_data, 0, data_desc->get_fft_size()*sizeof(float));
	this->gpgpu_format_data(input_data, formated_data, data_desc->projection);
	dump_cplx_dataset(formated_data, data_desc);

	int x_threads = data_desc->projection;
	int y_threads = data_desc->height/2;
	this->reading_Buf->readData(formated_data, global_data_size);
	this->prog->bindGlobal(reading_Buf);

    this->prog->setExeInfo(ProgExeInfo(uint4(0, 0, x_threads, y_threads)));
    Event *e = this->prog->run();
	e->waitEvent();
	gpgpu_deformat_data(formated_data, output_data, data_desc->projection);
	dump_real_dataset(output_data, data_desc);
	free(formated_data);
	return;
}


void GPGPU_FFT_Manager::gpgpu_format_data(float *input_data, float *formated_data, int proj) {

  cout<<"formating data ..."<<endl;
  int formated_data_size = proj*data_desc->height*data_desc->get_fft_row();
  memset(formated_data, 0, formated_data_size*sizeof(float));
  unsigned int index_base = 0;
  unsigned int fft_index_base = 0;
  unsigned int temp = 0;
  for(int i=0; i<proj;i++) {
	  for(int j=0; j<data_desc->height;j+=2) {
		  for(int k=0;k<data_desc->width;k++) {
			  temp = k;
			  if(switch_sheet[k]!=0)
				  temp = switch_sheet[k];
			  unsigned index = index_base + k;
			  unsigned fft_index = fft_index_base + temp*4; //real part and integete part
			  formated_data[fft_index] = input_data[index];
		  }
		  index_base+=data_desc->width;
		  
		  for(int k=0;k<data_desc->width;k++) {
			  temp = k;
			  if(switch_sheet[k]!=0)
				  temp = switch_sheet[k];
			  unsigned index = index_base + k;
			  unsigned fft_index = fft_index_base + temp*4 + 2; //real part and integete part
			  formated_data[fft_index] = input_data[index];
		  }
		  index_base+=data_desc->width;
		  fft_index_base += data_desc->get_fft_row()*2;
	  }
  }
}


void GPGPU_FFT_Manager::gpgpu_deformat_data(float *formated_data, float *output_data, int proj) {

	//dump_cplx_dataset(formated_data, data_desc);

	cout<<"deformating data ..."<<endl;
	unsigned int index_base = 0;
	unsigned int fft_index_base = 0;
	unsigned int temp = 0;
	for(int i=0; i<proj;i++) {
		for(int j=0; j<data_desc->height;j+=2) {
			for(int k=0;k<data_desc->width;k++) {
				unsigned index = index_base + k;
				unsigned fft_index = fft_index_base + k*4;
				output_data[index] = formated_data[fft_index];
			}

			index_base+=data_desc->width;

			for(int k=0;k<data_desc->width;k++) {
				unsigned index = index_base + k;
				unsigned fft_index = fft_index_base + k*4 + 2;
				output_data[index] = formated_data[fft_index];
			}
			index_base+=data_desc->width;
			fft_index_base += data_desc->get_fft_row()*2;
		}
	}

	return;
}


void GPGPU_FFT_Manager::gpgpu_exit() {
	
    this->progMgr->unloadProgram(prog);
    this->bufMgr->destroyBuffer(reading_Buf);
	this->bufMgr->destroyBuffer(computing_Buf);
    this->bufMgr->destroyBuffer(input_asm_Buf);
    this->bufMgr->releaseConstBuffer(const_Buf);
	this->bufMgr->destroyBuffer(in_Buf);
    //Runtime::destroy();
}



